"""
game.py - encapsulates the game state.

Copyright 2008 Roberto Nerici <roberton [at] bloggo.org>

This file is part of AsciiBlast.
AsciiBlast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
"""

import random
from starfield import Starfield
from ship import Ship, Autopilot
from asteroid import SmallAsteroid, LargeAsteroid
from gameobject import Gameobject

class Game:
    """
    Wraps up all the game state information for the active game.

    Currently main function is to have the list of all objects and
    to manage them all.
    """
    play_area_size = (60, 24)
    info_area_size = (20, 23)
    info_area_topleft = (60, 0)
    SCREEN_MINWIDTH = play_area_size[0] + info_area_size[0]
    SCREEN_MINHEIGHT = play_area_size[1]

    def __init__(self, stdscr, autopilot_on):
        """Initialise state and store curses window object."""
        # first some properties intended to be public
        self.screen_width = self.play_area_size[0]
        self.screen_height = self.play_area_size[1]
        self.stdscr = stdscr

        # now the private properties
        self._starfield = Starfield(self)
        self._tickcount = 0
        self._time_in_seconds = 0
        self._score = 0
        if autopilot_on:
            self._ship = Autopilot(self)
        else:
            self._ship = Ship(self)

        # prepare list of game objects
        self._gameobjects = []
        self.add_object(self._starfield)
        self.add_object(self._ship)
        
        # finally create the level object: this is responsible for creating
        # the items and objects that populate a level
        self._level = Level(self, 1)      # we start at level one!

    def tick(self, tickcount):
        """Tell all game objects to update themselves."""
        self._tickcount = tickcount

        for gameobject in self._gameobjects:
            assert(isinstance(gameobject, Gameobject))
            gameobject.tick(tickcount)

        # check if any game objects are marked for deletion
        self.remove_dead_objects()

        # debug: check for any remaining dead objects
        for gameobject in self._gameobjects:
            assert(gameobject.is_alive())

        # Ask the level if it needs to create any new game objects
        self._level.tick(tickcount)
        self._time_in_seconds = tickcount / 1000.0

    def draw(self):
        """Tell all game objects to draw themselves."""
        self.stdscr.erase()
        for gameobject in self._gameobjects:
            gameobject.draw(self.stdscr)

        # draw time and score in panel then refresh
        self._draw_panel()
        self._draw_time()
        self._draw_score()
        self._draw_shield(self._ship.shield())
        self.stdscr.refresh()

    def handle_key(self, keychar):
        """Give user's key press to appropriate game object: the ship!"""
        self._ship.handle_key(keychar)

    def add_object(self, new_object):
        """Adds a game object to the 'alive' list."""
        self._gameobjects.append(new_object)

    def check_for_hit(self, source, x, y, damage):
        """See if an object which can do damage has hit another object."""
        has_hit = False
        for gameobject in self._gameobjects:
            if id(source) != id(gameobject):
                if gameobject.handle_possible_hit(x, y, damage):
                    has_hit = True
        return has_hit

    def remove_dead_objects(self):
        """Removes objects that are no longer alive."""
        # pythonic way is to scan a temp list removing from real list
        temp_objects = self._gameobjects[:]
        for gameobject in temp_objects:
            if not gameobject.is_alive():
                self._gameobjects.remove(gameobject)

    def add_score(self, score):
        self._score += score

    def tickcount(self):
        return self._tickcount

    def over(self):
        # Check if ship destroyed: if so game over!
        return not self._ship.is_alive()

    def _draw_panel(self):
        """Display the static parts of the info panel."""
        self.stdscr.vline(
            self.info_area_topleft[1], self.info_area_topleft[0],
            "|", self.play_area_size[1])

    def _draw_time(self):
        """Display time to nearest 10th second."""
        display_time = str(int(10 * self._time_in_seconds) / 10.0)
        self.stdscr.addstr(
            self.info_area_size[1] - 1, self.info_area_topleft[0] + 9,
            display_time)

    def _draw_score(self):
        xpos = self.info_area_topleft[0] + 2
        self.stdscr.addstr(0, xpos, "Score")
        self.stdscr.addstr(1, xpos, str(self._score))
        pass

    def _draw_shield(self, strength):
        """Display the player's shield strength in the panel."""
        assert(strength <= 10 and strength >= 0)
        xpos = self.info_area_topleft[0] + 2
        self.stdscr.addstr(3, xpos, "Shield")
        self.stdscr.addstr(4, xpos, "##########"[0:strength])


class Level(object):
    def __init__(self, game, levelnumber):
        self._levelnumber = levelnumber
        self._game = game

    def tick(self, tickcount):
        ASTEROIDS_BEGIN = 3000
        if tickcount >= ASTEROIDS_BEGIN and \
            (tickcount % 3000 == 0 or tickcount % 2000 == 0):
            # generate an asteroid
            # start with small asteroids but gradually introduce large ones
            # until it is 50:50 chance of either
            blah = min(10000, tickcount - ASTEROIDS_BEGIN)
            if random.randrange(20000) < blah:
                new_asteroid = LargeAsteroid(self._game)
            else:
                new_asteroid = SmallAsteroid(self._game)
            self._game.add_object(new_asteroid)

